Skip to content

2823. 深度对象筛选

2823. 深度对象筛选

ts
type JSONValue =
  | null
  | boolean
  | number
  | string
  | JSONValue[]
  | { [key: string]: JSONValue }
type Obj = Record<string, JSONValue> | Array<JSONValue>

const isObject = (obj: any) => typeof obj === 'object' && obj !== null
function deepFilter(obj: Obj, fn: Function): Obj | undefined {
  if (!isObject(obj)) return fn(obj) ? obj : undefined

  if (Array.isArray(obj)) {
    let i = 0
    while (i < obj.length) {
      const val = deepFilter(obj[i] as any, fn)
      if (val === undefined) {
        obj.splice(i, 1) // 原地删除无效值
      } else {
        obj[i] = val
        i++
      }
    }
    return obj.length > 0 ? obj : undefined
  }

  for (const key in obj) {
    const val = obj[key]
    const filtered = deepFilter(val as any, fn)
    if (filtered === undefined) {
      delete obj[key]
    } else {
      obj[key] = filtered
    }
  }

  return Object.keys(obj).length > 0 ? obj : undefined
}

筛选与删除的传播

deepFilter 有一个重要设计:删除向上传播

基本类型不满足 fn → 返回 undefined。对于父级来说,子属性返回 undefined 意味着应该删除这个子属性(对象则 delete,数组则 splice)。

更关键的是:如果所有子属性都被删除,父级本身也返回 undefined,导致更上层删除父级。这就是「空容器向上传播」——一个对象如果所有子属性都不满足条件,它自己也没有存在的意义。

数组的 while 循环

数组用 while 循环 + splice 而非 filter,因为需要原地修改splice(i, 1) 删除元素后,索引 i 不递增——因为下一个元素会移到当前位置。只有当元素保留时才 i++

这是在中原地删除元素的经典模式。如果用 for 循环,删除后索引管理容易出错。

边界

  • 空数组 [] 如果所有元素被筛掉 → 返回 undefined
  • 空对象 {} → 返回 undefined
  • 筛选函数对容器的判断:deepFilter 不会用 fn 来判断容器本身(数组/对象),只判断基本类型和 null。容器通过「子元素是否都被筛光」这个间接标准来决定去留